iT邦幫忙

2022 iThome 鐵人賽

DAY 13
0

將可組合項作為可靠來源
如果狀態和邏輯比較簡單,在可組合項中使用界面邏輯界面和元素狀態是一種不錯的方法。例如,以下是處理 ScaffoldState 和 CoroutineScope 的 MyApp 可組合項:

@Composable
fun MyApp() {
    MyTheme {
        val scaffoldState = rememberScaffoldState()
        val coroutineScope = rememberCoroutineScope()

        Scaffold(scaffoldState = scaffoldState) {
            MyContent(
                showSnackbar = { message ->
                    coroutineScope.launch {
                        scaffoldState.snackbarHostState.showSnackbar(message)
                    }
                }
            )
        }
    }

ScaffedState包含可更改屬性,可在我的應用程序中添加相關的所有組件,可在我的應用程序項中進行更改。單一可靠來源的原則,而且將會對錯誤的追踪困難。

將狀態作為可信來源
當可包含顯示元素狀態的複雜性界面時,應將事務的組成部分給予支持。更合適的組合項時,以適當的方式進行狀態測試,降低可包含多個複雜性。關注點原則:可組合負責發出界面元素,而狀態包含包含分離邏輯和界面元素的狀態。

狀態包含可在組合項中創建和保存的普通類。狀態包含可遵循組合項的生命週期,因此可以採用 Compose 依賴項。

如果將可組合項作為可靠來源的部分中的 MyApp 組合項的增加,我們就可以創建一個 MyAppState 包含責任來管理其複雜性:

// Plain class that manages App's UI logic and UI elements' state
class MyAppState(
    val scaffoldState: ScaffoldState,
    val navController: NavHostController,
    private val resources: Resources,
    /* ... */
) {
    val bottomBarTabs = /* State */

    // Logic to decide when to show the bottom bar
    val shouldShowBottomBar: Boolean
        get() = /* ... */

    // Navigation logic, which is a type of UI logic
    fun navigateToBottomBarRoute(route: String) { /* ... */ }

    // Show snackbar using Resources
    fun showSnackbar(message: String) { /* ... */ }
}

@Composable
fun rememberMyAppState(
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    navController: NavHostController = rememberNavController(),
    resources: Resources = LocalContext.current.resources,
    /* ... */
) = remember(scaffoldState, navController, resources, /* ... */) {
    MyAppState(scaffoldState, navController, resources, /* ... */)
}

MyAppState 採用的是依賴,因此最好提供可記住組合中的 MyAppState 實例項的方法。在上面的示例中為記住 MyAppState 函數。

現在,MyApp 決定以狀態發出界面元素,將所有界面邏輯和界面元素的委派給 MyAppState:

如您所見,增加可組合的項目會增加對狀態容器的需求。這些責任可能存在於界面邏輯中,也可能只與跟踪的狀態數相關的責任。

將 ViewModel 作為可信來源
如果普通狀態容器類負責界面邏輯及界面元素的狀態,則 ViewModel 是一種特殊狀態容器類型,其負責:

提供對應用的業務邏輯的訪問權限,該邏輯通常在層次結構的其他層(例如業務層和數據層)中;
準備在特定屏幕上呈現狀態上的數據,這些數據會成為屏幕或界面。
ViewModel 的生命週期比組合長,仍然是它們在發生配置變化後的有效原因。ViewModel 可以按照您的內容(即活動或片段)的主人的生命週期,也可以遵循或導航圖的生命週期(如果就是使用導航。查看模型到生命週期的長期調用組合,因此保留對綁定週期的狀態。

我們建議的模型級可組合使用 View 向下傳遞到來提供對業務邏輯訪問的實例並顯示狀態的可信來源作為此項。您將 ViewModel 需要其他可組合項。如了解 ViewModel 的原因適用於種情況,請參見 ViewModel 和狀態容器部分。

下面是在屏幕級可組合項中使用的 ViewModel 示例:

data class ExampleUiState(
    val dataToDisplayOnScreen: List<Example> = emptyList(),
    val userMessages: List<Message> = emptyList(),
    val loading: Boolean = false
)

class ExampleViewModel(
    private val repository: MyRepository,
    private val savedState: SavedStateHandle
) : ViewModel() {

    var uiState by mutableStateOf(ExampleUiState())
        private set

    // Business logic
    fun somethingRelatedToBusinessLogic() { /* ... */ }
}

@Composable
fun ExampleScreen(viewModel: ExampleViewModel = viewModel()) {

    val uiState = viewModel.uiState
    /* ... */

    ExampleReusableComponent(
        someData = uiState.dataToDisplayOnScreen,
        onDoSomething = { viewModel.somethingRelatedToBusinessLogic() }
    )
}

@Composable
fun ExampleReusableComponent(someData: Any, onDoSomething: () -> Unit) {
    /* ... */
    Button(onClick = onDoSomething) {
        Text("Do something")
    }
}

ViewModel 和容器狀態
ViewModel 在中的優勢適用於適用於在屏幕上提供以下權限開發對業務邏輯的訪問以及適用於 Android 的應用前景。

ViewModel 觸發的操作在配置發生變化後仍然有效。
與導航集成:
當屏幕位於返回中時,導航會在返回時緩存項的查看模式。這對在位置時會立即提供加載的數據非常重要。使用跟踪目標可組合組合週期週期的狀態時,這種情況下更難處理。
當出現後返回的位置,查看模型也會被項並,以確保自動清理處理。這可能是不同的組合,監聽目標的可能有變化,例如新的原因、發生的原因、發生的變化等。
與其他 Jetpack 庫(如 Hilt)集成。

容器可組合,ViewModel 容器的屏幕級可組合項與正常狀態條件可訪問一個 ViewModel 來對業務邏輯的狀態,提供一個狀態容器來管理其邏輯和界面由於 ViewModel 的生命週期比狀態長,因此可以根據需要將 ViewModel 的狀態包含在依賴項中。

下面的代碼展示了在 ExampleScreen 上協同工作的 ViewModel 和普通容器:

class ExampleState(
val lazyListState: LazyListState,
private val resources: Resources,
private val expandedItems: List = emptyList()
) {
fun isExpandedItem(item: Item): Boolean = TODO()
/* ... */
}

@Composable
fun rememberExampleState(/* ... */): ExampleState { TODO() }

@Composable
fun ExampleScreen(viewModel: ExampleViewModel = viewModel()) {

val uiState = viewModel.uiState
val exampleState = rememberExampleState()

LazyColumn(state = exampleState.lazyListState) {
    items(uiState.dataToDisplayOnScreen) { item ->
        if (exampleState.isExpandedItem(item)) {
            /* ... */
        }
        /* ... */
    }
}

}


上一篇
[Day12] Compose 的狀態管理 (三)
系列文
30天 Android Jetpack Compose 奇幻旅程13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言